/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/tests/runtime/common/test_helper.h"

#include <cstddef>

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/napi/include/jsnapi.h"
#include "plugins/ecmascript/runtime/napi/jsnapi_helper-inl.h"
#include "plugins/ecmascript/runtime/object_factory.h"

#include "libpandabase/os/thread.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

// NOLINTBEGIN(readability-magic-numbers,modernize-avoid-c-arrays)

namespace ark::test {
class JSNApiTests : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        RuntimeOption option;
        option.SetGcType(RuntimeOption::GcType::STW);
        option.SetLogLevel(RuntimeOption::LogLevel::ERROR);
        vm_ = JSNApi::CreateJSVM(option);
        ASSERT_TRUE(vm_ != nullptr) << "Cannot create Runtime";
        thread_ = vm_->GetAssociatedJSThread();
        vm_->GetFactory()->SetTriggerGc(true);
    }

    void TearDown() override
    {
        vm_->GetFactory()->SetTriggerGc(false);
        JSNApi::DestroyJSVM(vm_);
    }

    Local<ObjectRef> NewObjectByConstructor(Local<FunctionRef> ctor)
    {
        ScopedManagedCodeThread scope(thread_);
        auto factory = vm_->GetFactory();
        JSHandle<JSTaggedValue> ctorHandle = JSNApiHelper::ToJSHandle(ctor);
        JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(ctorHandle), ctorHandle);
        return JSNApiHelper::ToLocal<ObjectRef>(JSHandle<JSTaggedValue>(obj));
    }

    Local<FunctionRef> NewFunctionByHClass(JSHandle<JSHClass> hclass, JSMethod *method)
    {
        ScopedManagedCodeThread scope(thread_);
        auto factory = vm_->GetFactory();
        JSHandle<JSFunction> protoFunc = factory->NewJSFunctionByDynClass(method, hclass);
        return JSNApiHelper::ToLocal<FunctionRef>(JSHandle<JSTaggedValue>(protoFunc));
    }

    Local<JSValueRef> GetMethod(Local<ObjectRef> obj, Local<ObjectRef> key)
    {
        JSHandle<JSTaggedValue> val =
            JSObject::GetMethod(thread_, JSNApiHelper::ToJSHandle(obj), JSNApiHelper::ToJSHandle(key));
        return JSNApiHelper::ToLocal<JSValueRef>(val);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ = nullptr;
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    EcmaVM *vm_ = nullptr;
};

Local<JSValueRef> FunctionCallback(EcmaVM *vm, Local<JSValueRef> /*unused*/, const Local<JSValueRef> * /*unused*/,
                                   int32_t length, void * /*unused*/)
{
    EscapeLocalScope scope(vm);
    return scope.Escape(ArrayRef::New(vm, length));
}

void ThreadCheck(const EcmaVM *vm)
{
    EXPECT_TRUE(vm->GetAssociatedJSThread()->GetId() != os::thread::GetCurrentThreadId());
}

TEST_F(JSNApiTests, GetGlobalObject)
{
    LocalScope scope(vm_);
    Local<ObjectRef> globalObject = JSNApi::GetGlobalObject(vm_);
    ASSERT_FALSE(globalObject.IsEmpty());
    ASSERT_TRUE(globalObject->IsObject());
}

TEST_F(JSNApiTests, ThreadIdCheck)
{
    EXPECT_TRUE(vm_->GetAssociatedJSThread()->GetId() == os::thread::GetCurrentThreadId());
    std::thread testThread(ThreadCheck, vm_);
    testThread.join();
}

TEST_F(JSNApiTests, RegisterFunction)
{
    LocalScope scope(vm_);
    Local<FunctionRef> callback = FunctionRef::New(vm_, FunctionCallback, nullptr);
    ASSERT_TRUE(!callback.IsEmpty());
    std::vector<Local<JSValueRef>> arguments;
    arguments.emplace_back(JSValueRef::Undefined(vm_));
    Local<JSValueRef> result = callback->Call(vm_, JSValueRef::Undefined(vm_), arguments.data(), arguments.size());
    ASSERT_TRUE(result->IsArray(vm_));
    Local<ArrayRef> array(result);
    ASSERT_EQ(array->Length(vm_), arguments.size());
}

TEST_F(JSNApiTests, GetProperty)
{
    LocalScope scope(vm_);
    Local<ObjectRef> globalObject = JSNApi::GetGlobalObject(vm_);
    ASSERT_FALSE(globalObject.IsEmpty());
    ASSERT_TRUE(globalObject->IsObject());

    Local<ObjectRef> key = StringRef::NewFromUtf8(vm_, "Number");
    Local<ObjectRef> property = globalObject->Get(vm_, key);
    ASSERT_TRUE(property->IsFunction());
}

TEST_F(JSNApiTests, SetProperty)
{
    LocalScope scope(vm_);
    Local<ObjectRef> globalObject = JSNApi::GetGlobalObject(vm_);
    ASSERT_FALSE(globalObject.IsEmpty());
    ASSERT_TRUE(globalObject->IsObject());

    Local<ArrayRef> property = ArrayRef::New(vm_, 3);  // 3 : length
    ASSERT_TRUE(property->IsArray(vm_));
    ASSERT_EQ(property->Length(vm_), 3);  // 3 : test case of input

    Local<ObjectRef> key = StringRef::NewFromUtf8(vm_, "Test");
    bool result = globalObject->Set(vm_, key, property);
    ASSERT_TRUE(result);

    Local<ObjectRef> propertyGet = globalObject->Get(vm_, key);
    ASSERT_TRUE(propertyGet->IsArray(vm_));
    ASSERT_EQ(Local<ArrayRef>(propertyGet)->Length(vm_), 3);  // 3 : test case of input
}

TEST_F(JSNApiTests, JsonParser)
{
    LocalScope scope(vm_);
    Local<ObjectRef> globalObject = JSNApi::GetGlobalObject(vm_);
    ASSERT_FALSE(globalObject.IsEmpty());
    ASSERT_TRUE(globalObject->IsObject());

    const char *const test {R"({"orientation": "portrait"})"};
    Local<ObjectRef> jsonString = StringRef::NewFromUtf8(vm_, test);

    Local<JSValueRef> result = JSON::Parse(vm_, jsonString);
    ASSERT_TRUE(result->IsObject());

    Local<ObjectRef> keyString = StringRef::NewFromUtf8(vm_, "orientation");
    Local<JSValueRef> property = Local<ObjectRef>(result)->Get(vm_, keyString);
    ASSERT_TRUE(property->IsString());
}

TEST_F(JSNApiTests, StrictEqual)
{
    LocalScope scope(vm_);
    Local<StringRef> origin = StringRef::NewFromUtf8(vm_, "1");
    Local<StringRef> target1 = StringRef::NewFromUtf8(vm_, "1");
    Local<NumberRef> target = NumberRef::New(vm_, 1);

    ASSERT_FALSE(origin->IsStrictEquals(vm_, target));
    ASSERT_TRUE(origin->IsStrictEquals(vm_, target1));
}

TEST_F(JSNApiTests, InstanceOf)
{
    LocalScope scope(vm_);
    Local<FunctionRef> target = FunctionRef::New(vm_, nullptr, nullptr);
    Local<ArrayRef> origin = ArrayRef::New(vm_, 1);

    ASSERT_FALSE(origin->InstanceOf(vm_, target));
}

TEST_F(JSNApiTests, TypeOf)
{
    LocalScope scope(vm_);
    Local<StringRef> origin = StringRef::NewFromUtf8(vm_, "1");
    Local<StringRef> typeString = origin->Typeof(vm_);
    ASSERT_EQ(typeString->ToString(), "string");

    Local<NumberRef> target = NumberRef::New(vm_, 1);
    typeString = target->Typeof(vm_);
    ASSERT_EQ(typeString->ToString(), "number");
}

TEST_F(JSNApiTests, Symbol)
{
    LocalScope scope(vm_);
    Local<StringRef> description = StringRef::NewFromUtf8(vm_, "test");
    Local<SymbolRef> symbol = SymbolRef::New(vm_, description);

    ASSERT_FALSE(description->IsSymbol());
    ASSERT_TRUE(symbol->IsSymbol());
}

TEST_F(JSNApiTests, StringUtf8_001)
{
    LocalScope scope(vm_);
    std::string test = "Hello world";
    Local<StringRef> testString = StringRef::NewFromUtf8(vm_, test.c_str());

    EXPECT_TRUE(testString->Utf8Length() == 12);           // 12 : length of testString("Hello World")
    char buffer[12];                                       // 12 : length of testString
    EXPECT_TRUE(testString->WriteUtf8(buffer, 12) == 12);  // 12 : length of testString("Hello World")
    std::string res(buffer);
    ASSERT_EQ(res, test);
}

TEST_F(JSNApiTests, StringUtf8_002)
{
    LocalScope scope(vm_);
    std::string test = "年";
    Local<StringRef> testString = StringRef::NewFromUtf8(vm_, test.c_str());

    EXPECT_TRUE(testString->Utf8Length() == 4);          // 4 : length of testString("年")
    char buffer[4];                                      // 4 : length of testString
    EXPECT_TRUE(testString->WriteUtf8(buffer, 4) == 4);  // 4 : length of testString("年")
    std::string res(buffer);
    ASSERT_EQ(res, test);
}

TEST_F(JSNApiTests, ToType)
{
    LocalScope scope(vm_);
    Local<StringRef> toString = StringRef::NewFromUtf8(vm_, "-123.3");
    Local<JSValueRef> toValue(toString);

    ASSERT_EQ(toString->ToNumber(vm_)->Value(), -123.3);  // -123 : test case of input
    ASSERT_EQ(toString->ToBoolean(vm_)->Value(), true);
    ASSERT_EQ(toValue->ToString(vm_)->ToString(), "-123.3");
    ASSERT_TRUE(toValue->ToObject(vm_)->IsObject());
}

TEST_F(JSNApiTests, TypeValue)
{
    LocalScope scope(vm_);
    Local<StringRef> toString = StringRef::NewFromUtf8(vm_, "-123");
    Local<JSValueRef> toValue(toString);

    ASSERT_EQ(toString->Int32Value(vm_), -123);  // -123 : test case of input
    ASSERT_EQ(toString->BooleaValue(), true);
    ASSERT_EQ(toString->Uint32Value(vm_), 4294967173);  // 4294967173 : test case of input
    ASSERT_EQ(toString->IntegerValue(vm_), -123);       // -123 : test case of input
}

TEST_F(JSNApiTests, DefineProperty)
{
    LocalScope scope(vm_);
    Local<ObjectRef> object = ObjectRef::New(vm_);
    Local<JSValueRef> key = StringRef::NewFromUtf8(vm_, "TestKey");
    Local<JSValueRef> value = ObjectRef::New(vm_);
    PropertyAttribute attribute(value, true, true, true);

    ASSERT_TRUE(object->DefineProperty(vm_, key, attribute));
    Local<JSValueRef> value1 = object->Get(vm_, key);
    ASSERT_TRUE(value->IsStrictEquals(vm_, value1));
}

TEST_F(JSNApiTests, HasProperty)
{
    LocalScope scope(vm_);
    Local<ObjectRef> object = ObjectRef::New(vm_);
    Local<JSValueRef> key = StringRef::NewFromUtf8(vm_, "TestKey");
    Local<JSValueRef> value = ObjectRef::New(vm_);
    PropertyAttribute attribute(value, true, true, true);

    ASSERT_TRUE(object->DefineProperty(vm_, key, attribute));
    ASSERT_TRUE(object->Has(vm_, key));
}

TEST_F(JSNApiTests, DeleteProperty)
{
    LocalScope scope(vm_);
    Local<ObjectRef> object = ObjectRef::New(vm_);
    Local<JSValueRef> key = StringRef::NewFromUtf8(vm_, "TestKey");
    Local<JSValueRef> value = ObjectRef::New(vm_);
    PropertyAttribute attribute(value, true, true, true);

    ASSERT_TRUE(object->DefineProperty(vm_, key, attribute));
    ASSERT_TRUE(object->Delete(vm_, key));
    ASSERT_FALSE(object->Has(vm_, key));
}

TEST_F(JSNApiTests, GetProtoType)
{
    LocalScope scope(vm_);
    Local<FunctionRef> function = FunctionRef::New(vm_, nullptr, nullptr);
    Local<JSValueRef> protoType = function->GetPrototype(vm_);
    ASSERT_TRUE(protoType->IsObject());

    Local<FunctionRef> object = ObjectRef::New(vm_);
    protoType = object->GetPrototype(vm_);
    ASSERT_TRUE(protoType->IsObject());
}

void CheckReject(EcmaVM * /*unused*/, Local<JSValueRef> /*unused*/, const Local<JSValueRef> argv[], int32_t length,
                 void * /*unused*/)
{
    ASSERT_EQ(length, 1);
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    Local<JSValueRef> reason = argv[0];
    ASSERT_TRUE(reason->IsString());
    ASSERT_EQ(Local<StringRef>(reason)->ToString(), "Reject");
}

Local<JSValueRef> RejectCallback(EcmaVM *vm, Local<JSValueRef> thisArg, const Local<JSValueRef> argv[], int32_t length,
                                 void *data)
{
    LocalScope scope(vm);
    CheckReject(vm, thisArg, argv, length, data);
    return JSValueRef::Undefined(vm);
}

TEST_F(JSNApiTests, PromiseCatch)
{
    LocalScope scope(vm_);
    Local<PromiseCapabilityRef> capability = PromiseCapabilityRef::New(vm_);

    Local<PromiseRef> promise = capability->GetPromise(vm_);
    Local<FunctionRef> reject = FunctionRef::New(vm_, RejectCallback, nullptr);
    Local<PromiseRef> catchPromise = promise->Catch(vm_, reject);
    ASSERT_TRUE(promise->IsPromise());
    ASSERT_TRUE(catchPromise->IsPromise());

    Local<StringRef> reason = StringRef::NewFromUtf8(vm_, "Reject");
    ASSERT_TRUE(capability->Reject(vm_, reason));

    JSNApi::ExecutePendingJob(vm_);
}

void CheckResolve(EcmaVM * /*unused*/, Local<JSValueRef> /*unused*/, const Local<JSValueRef> argv[], int32_t length,
                  void * /*unused*/)
{
    ASSERT_EQ(length, 1);
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    Local<JSValueRef> value = argv[0];
    ASSERT_TRUE(value->IsNumber());
    ASSERT_EQ(Local<NumberRef>(value)->Value(), 300.3);  // 300.3 : test case of input
}

Local<JSValueRef> ResolvedCallback(EcmaVM *vm, Local<JSValueRef> thisArg, const Local<JSValueRef> argv[],
                                   int32_t length, void *data)
{
    LocalScope scope(vm);
    CheckResolve(vm, thisArg, argv, length, data);
    return JSValueRef::Undefined(vm);
}

TEST_F(JSNApiTests, PromiseThen)
{
    LocalScope scope(vm_);
    Local<PromiseCapabilityRef> capability = PromiseCapabilityRef::New(vm_);

    Local<PromiseRef> promise = capability->GetPromise(vm_);
    Local<FunctionRef> resolve = FunctionRef::New(vm_, ResolvedCallback, nullptr);
    Local<FunctionRef> reject = FunctionRef::New(vm_, RejectCallback, nullptr);
    Local<PromiseRef> thenPromise = promise->Then(vm_, resolve, reject);
    ASSERT_TRUE(promise->IsPromise());
    ASSERT_TRUE(thenPromise->IsPromise());

    Local<StringRef> value = NumberRef::New(vm_, 300.3);  // 300.3 : test case of input
    ASSERT_TRUE(capability->Resolve(vm_, value));
    JSNApi::ExecutePendingJob(vm_);
}

TEST_F(JSNApiTests, Constructor)
{
    LocalScope scope(vm_);
    Local<ObjectRef> object = JSNApi::GetGlobalObject(vm_);
    Local<StringRef> key = StringRef::NewFromUtf8(vm_, "Number");
    Local<FunctionRef> numberConstructor = object->Get(vm_, key);
    Local<JSValueRef> argv[1];
    argv[0] = NumberRef::New(vm_, 1.3);  // 1.3 : test case of input
    Local<JSValueRef> result = numberConstructor->Constructor(vm_, argv, 1);
    ASSERT_TRUE(result->IsObject());
    ASSERT_EQ(result->ToNumber(vm_)->Value(), 1.3);  // 1.3 : size of arguments
}

TEST_F(JSNApiTests, ArrayBuffer)
{
    LocalScope scope(vm_);
    const int32_t length = 15;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());
    ASSERT_EQ(arrayBuffer->ByteLength(vm_), length);
    ASSERT_NE(arrayBuffer->GetBuffer(), nullptr);
    JSNApi::TriggerGC(vm_);
}

TEST_F(JSNApiTests, ArrayBufferWithBuffer)
{
    static bool isFree = false;
    struct Data {
        int32_t length;
    };
    const int32_t length = 15;
    Data *data = new Data();
    data->length = length;
    Deleter deleter = [](void *buffer, void *dataPtr) -> void {
        delete[] reinterpret_cast<uint8_t *>(buffer);
        Data *currentData = reinterpret_cast<Data *>(dataPtr);
        ASSERT_EQ(currentData->length, 15);  // 5 : size of arguments
        delete currentData;
        isFree = true;
    };
    {
        LocalScope scope(vm_);
        auto *buffer = new uint8_t[length]();
        Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, buffer, length, deleter, data);
        ASSERT_TRUE(arrayBuffer->IsArrayBuffer());
        ASSERT_EQ(arrayBuffer->ByteLength(vm_), length);
        ASSERT_EQ(arrayBuffer->GetBuffer(), buffer);
    }
    JSNApi::TriggerGC(vm_);
    ASSERT_TRUE(isFree);
}

TEST_F(JSNApiTests, DataView)
{
    LocalScope scope(vm_);
    const int32_t length = 15;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    JSNApi::TriggerGC(vm_);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 5 : offset of byte, 7 : length
    Local<DataViewRef> dataView = DataViewRef::New(vm_, arrayBuffer, 5, 7);
    ASSERT_TRUE(dataView->IsDataView());
    ASSERT_EQ(dataView->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
    ASSERT_EQ(dataView->ByteLength(), 7);  // 7 : size of arguments
    ASSERT_EQ(dataView->ByteOffset(), 5);  // 5 : size of arguments

    // 5 : offset of byte, 11 : length
    dataView = DataViewRef::New(vm_, arrayBuffer, 5, 11);
    ASSERT_TRUE(dataView->IsException());
}

TEST_F(JSNApiTests, Int8Array)
{
    LocalScope scope(vm_);
    const int32_t length = 15;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 5 : offset of byte, 6 : length
    Local<Int8ArrayRef> typedArray = Int8ArrayRef::New(vm_, arrayBuffer, 5, 6);
    ASSERT_TRUE(typedArray->IsInt8Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 6);   // 6 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 5);   // 5 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Uint8Array)
{
    LocalScope scope(vm_);
    const int32_t length = 15;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 5 : offset of byte, 6 : length
    Local<Uint8ArrayRef> typedArray = Uint8ArrayRef::New(vm_, arrayBuffer, 5, 6);
    ASSERT_TRUE(typedArray->IsUint8Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 6);   // 6 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 5);   // 5 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Uint8ClampedArray)
{
    LocalScope scope(vm_);
    const int32_t length = 15;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 5 : offset of byte, 6 : length
    Local<Uint8ClampedArrayRef> typedArray = Uint8ClampedArrayRef::New(vm_, arrayBuffer, 5, 6);
    ASSERT_TRUE(typedArray->IsUint8ClampedArray());
    ASSERT_EQ(typedArray->ByteLength(vm_), 6);   // 6 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 5);   // 5 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Int16Array)
{
    LocalScope scope(vm_);
    const int32_t length = 30;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 4 : offset of byte, 6 : length
    Local<Int16ArrayRef> typedArray = Int16ArrayRef::New(vm_, arrayBuffer, 4, 6);
    ASSERT_TRUE(typedArray->IsInt16Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 12);  // 12 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 4);   // 4 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Uint16Array)
{
    LocalScope scope(vm_);
    const int32_t length = 30;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 4 : offset of byte, 6 : length
    Local<Uint16ArrayRef> typedArray = Uint16ArrayRef::New(vm_, arrayBuffer, 4, 6);
    ASSERT_TRUE(typedArray->IsUint16Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 12);  // 12 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 4);   // 4 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Uint32Array)
{
    LocalScope scope(vm_);
    const int32_t length = 30;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 4 : offset of byte, 6 : length
    Local<Uint32ArrayRef> typedArray = Uint32ArrayRef::New(vm_, arrayBuffer, 4, 6);
    ASSERT_TRUE(typedArray->IsUint32Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 24);  // 24 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 4);   // 4 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Int32Array)
{
    LocalScope scope(vm_);
    const int32_t length = 30;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 4 : offset of byte, 6 : length
    Local<Int32ArrayRef> typedArray = Int32ArrayRef::New(vm_, arrayBuffer, 4, 6);
    ASSERT_TRUE(typedArray->IsInt32Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 24);  // 24 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 4);   // 4 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Float32Array)
{
    LocalScope scope(vm_);
    const int32_t length = 30;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 4 : offset of byte, 6 : length
    Local<Float32ArrayRef> typedArray = Float32ArrayRef::New(vm_, arrayBuffer, 4, 6);
    ASSERT_TRUE(typedArray->IsFloat32Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 24);  // 24 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 4);   // 4 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Float64Array)
{
    LocalScope scope(vm_);
    const int32_t length = 57;
    Local<ArrayBufferRef> arrayBuffer = ArrayBufferRef::New(vm_, length);
    ASSERT_TRUE(arrayBuffer->IsArrayBuffer());

    // 8 : offset of byte, 6 : length
    Local<Float64ArrayRef> typedArray = Float64ArrayRef::New(vm_, arrayBuffer, 8, 6);
    ASSERT_TRUE(typedArray->IsFloat64Array());
    ASSERT_EQ(typedArray->ByteLength(vm_), 48);  // 48 : length of bytes
    ASSERT_EQ(typedArray->ByteOffset(vm_), 8);   // 8 : offset of byte
    ASSERT_EQ(typedArray->ArrayLength(vm_), 6);  // 6 : length of array
    ASSERT_EQ(typedArray->GetArrayBuffer(vm_)->GetBuffer(), arrayBuffer->GetBuffer());
}

TEST_F(JSNApiTests, Error)
{
    LocalScope scope(vm_);
    Local<StringRef> message = StringRef::NewFromUtf8(vm_, "ErrorTest");
    Local<JSValueRef> error = Exception::Error(vm_, message);
    ASSERT_TRUE(error->IsError());

    JSNApi::ThrowException(vm_, error);
    ASSERT_TRUE(thread_->HasPendingException());
}

TEST_F(JSNApiTests, RangeError)
{
    LocalScope scope(vm_);
    Local<StringRef> message = StringRef::NewFromUtf8(vm_, "ErrorTest");
    Local<JSValueRef> error = Exception::RangeError(vm_, message);
    ASSERT_TRUE(error->IsError());

    JSNApi::ThrowException(vm_, error);
    ASSERT_TRUE(thread_->HasPendingException());
}

TEST_F(JSNApiTests, TypeError)
{
    LocalScope scope(vm_);
    Local<StringRef> message = StringRef::NewFromUtf8(vm_, "ErrorTest");
    Local<JSValueRef> error = Exception::TypeError(vm_, message);
    ASSERT_TRUE(error->IsError());

    JSNApi::ThrowException(vm_, error);
    ASSERT_TRUE(thread_->HasPendingException());
}

TEST_F(JSNApiTests, ReferenceError)
{
    LocalScope scope(vm_);
    Local<StringRef> message = StringRef::NewFromUtf8(vm_, "ErrorTest");
    Local<JSValueRef> error = Exception::ReferenceError(vm_, message);
    ASSERT_TRUE(error->IsError());

    JSNApi::ThrowException(vm_, error);
    ASSERT_TRUE(thread_->HasPendingException());
}

TEST_F(JSNApiTests, SyntaxError)
{
    LocalScope scope(vm_);
    Local<StringRef> message = StringRef::NewFromUtf8(vm_, "ErrorTest");
    Local<JSValueRef> error = Exception::SyntaxError(vm_, message);
    ASSERT_TRUE(error->IsError());

    JSNApi::ThrowException(vm_, error);
    ASSERT_TRUE(thread_->HasPendingException());
}

TEST_F(JSNApiTests, InheritPrototype_001)
{
    LocalScope scope(vm_);
    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();
    // new with Builtins::Set Prototype
    JSHandle<JSTaggedValue> set = env->GetSetFunction();
    Local<FunctionRef> setLocal = JSNApiHelper::ToLocal<FunctionRef>(set);
    // new with Builtins::Map Prototype
    JSHandle<JSTaggedValue> map = env->GetMapFunction();
    Local<FunctionRef> mapLocal = JSNApiHelper::ToLocal<FunctionRef>(map);
    JSHandle<JSTaggedValue> setPrototype(thread_, JSHandle<JSFunction>::Cast(set)->GetFunctionPrototype());
    JSHandle<JSTaggedValue> mapPrototype(thread_, JSHandle<JSFunction>::Cast(map)->GetFunctionPrototype());
    JSHandle<JSTaggedValue> mapPrototypeProto(thread_, JSHandle<JSObject>::Cast(mapPrototype)->GetPrototype(thread_));
    bool same = JSTaggedValue::SameValue(setPrototype, mapPrototypeProto);
    // before inherit, map.Prototype.__proto__ should be different from set.Prototype
    ASSERT_FALSE(same);
    // before inherit, map.__proto__ should be different from set
    JSHandle<JSTaggedValue> mapProto(thread_, JSHandle<JSObject>::Cast(map)->GetPrototype(thread_));
    bool same1 = JSTaggedValue::SameValue(set, mapProto);
    ASSERT_FALSE(same1);

    // Set property to Set Function
    Local<ObjectRef> defaultString =
        JSNApiHelper::ToLocal<ObjectRef>(thread_->GlobalConstants()->GetHandledDefaultString());
    {
        PropertyAttribute attr(defaultString, true, true, true);
        ASSERT_TRUE(setLocal->DefineProperty(vm_, defaultString, attr));
    }
    Local<ObjectRef> func = JSNApiHelper::ToLocal<ObjectRef>(env->GetTypedArrayFunction());
    Local<StringRef> property1String = StringRef::NewFromUtf8(vm_, "property1");
    {
        PropertyAttribute attr(func, true, true, true);
        ASSERT_TRUE(setLocal->DefineProperty(vm_, property1String, attr));
    }

    mapLocal->Inherit(vm_, setLocal);
    JSHandle<JSTaggedValue> sonHandle = JSNApiHelper::ToJSHandle(mapLocal);
    JSHandle<JSTaggedValue> sonPrototype(thread_, JSHandle<JSFunction>::Cast(sonHandle)->GetFunctionPrototype());
    JSHandle<JSTaggedValue> sonPrototypeProto(thread_, JSHandle<JSObject>::Cast(sonPrototype)->GetPrototype(thread_));
    bool same2 = JSTaggedValue::SameValue(setPrototype, sonPrototypeProto);
    ASSERT_TRUE(same2);
    JSHandle<JSTaggedValue> sonProto(thread_, JSHandle<JSObject>::Cast(map)->GetPrototype(thread_));
    bool same3 = JSTaggedValue::SameValue(set, sonProto);
    ASSERT_TRUE(same3);

    // son = new Son(), Son() inherit from Parent(), Test whether son.InstanceOf(Parent) is true
    Local<ObjectRef> sonObj = NewObjectByConstructor(mapLocal);
    bool isInstance = sonObj->InstanceOf(vm_, setLocal);
    ASSERT_TRUE(isInstance);

    // Test whether son Function can access to property of parent Function
    Local<JSValueRef> res = mapLocal->Get(vm_, defaultString);
    ASSERT_TRUE(defaultString->IsStrictEquals(vm_, res));
    Local<JSValueRef> res1 = mapLocal->Get(vm_, property1String);
    ASSERT_TRUE(func->IsStrictEquals(vm_, res1));

    // new with empty Function Constructor
    Local<FunctionRef> son1 = FunctionRef::New(vm_, FunctionCallback, nullptr);
    son1->Inherit(vm_, mapLocal);
    JSHandle<JSFunction> son1Handle = JSHandle<JSFunction>::Cast(JSNApiHelper::ToJSHandle(son1));
    ASSERT_TRUE(son1Handle->HasFunctionPrototype());
}

TEST_F(JSNApiTests, InheritPrototype_002)
{
    LocalScope scope(vm_);
    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();
    // new with Builtins::weakSet Prototype
    JSHandle<JSTaggedValue> weakSet = env->GetWeakSetFunction();
    Local<FunctionRef> weakSetLocal = JSNApiHelper::ToLocal<FunctionRef>(weakSet);
    // new with Builtins::weakMap Prototype
    JSHandle<JSTaggedValue> weakMap = env->GetWeakMapFunction();
    Local<FunctionRef> weakMapLocal = JSNApiHelper::ToLocal<FunctionRef>(weakMap);

    weakMapLocal->Inherit(vm_, weakSetLocal);

    Local<StringRef> property1String = StringRef::NewFromUtf8(vm_, "property1");
    Local<ObjectRef> func = JSNApiHelper::ToLocal<ObjectRef>(env->GetArrayFunction());
    {
        PropertyAttribute attr(func, true, true, true);
        ASSERT_TRUE(weakMapLocal->DefineProperty(vm_, property1String, attr));
    }

    Local<ObjectRef> sonObj = NewObjectByConstructor(weakMapLocal);

    Local<ObjectRef> fatherObj = NewObjectByConstructor(weakSetLocal);

    Local<JSValueRef> sonMethod = GetMethod(sonObj, property1String);
    Local<JSValueRef> fatherMethod = GetMethod(fatherObj, property1String);
    ASSERT_TRUE(sonMethod->IsStrictEquals(vm_, fatherMethod));
}

TEST_F(JSNApiTests, InheritPrototype_003)
{
    LocalScope scope(vm_);
    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();

    JSMethod *invokeSelf =
        vm_->GetMethodForNativeFunction(reinterpret_cast<void *>(builtins::function::FunctionPrototypeInvokeSelf));
    // father type
    Local<FunctionRef> protoLocal =
        NewFunctionByHClass(JSHandle<JSHClass>::Cast(env->GetFunctionClassWithProto()), invokeSelf);
    // son type
    Local<FunctionRef> noProtoLocal =
        NewFunctionByHClass(JSHandle<JSHClass>::Cast(env->GetFunctionClassWithoutProto()), invokeSelf);

    JSHandle<JSFunction> sonHandle = JSHandle<JSFunction>::Cast(JSNApiHelper::ToJSHandle(noProtoLocal));
    EXPECT_FALSE(sonHandle->HasFunctionPrototype());

    Local<ObjectRef> defaultString =
        JSNApiHelper::ToLocal<ObjectRef>(thread_->GlobalConstants()->GetHandledDefaultString());
    {
        PropertyAttribute attr(defaultString, true, true, true);
        protoLocal->DefineProperty(vm_, defaultString, attr);
    }

    noProtoLocal->Inherit(vm_, protoLocal);
    JSHandle<JSFunction> son1Handle = JSHandle<JSFunction>::Cast(JSNApiHelper::ToJSHandle(noProtoLocal));
    EXPECT_TRUE(son1Handle->HasFunctionPrototype());

    Local<JSValueRef> res = noProtoLocal->Get(vm_, defaultString);
    EXPECT_TRUE(defaultString->IsStrictEquals(vm_, res));

    Local<StringRef> propertyString = StringRef::NewFromUtf8(vm_, "property");
    Local<ObjectRef> func = JSNApiHelper::ToLocal<ObjectRef>(env->GetArrayFunction());
    {
        PropertyAttribute attr(func, true, true, true);
        protoLocal->DefineProperty(vm_, propertyString, attr);
    }
    Local<JSValueRef> res1 = noProtoLocal->Get(vm_, propertyString);
    EXPECT_TRUE(func->IsStrictEquals(vm_, res1));
}

TEST_F(JSNApiTests, DISABLED_InheritPrototype_004)  // NOTE(vpukhov)
{
    LocalScope scope(vm_);
    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();
    auto factory = vm_->GetFactory();

    JSHandle<JSTaggedValue> weakSet = env->GetWeakSetFunction();
    JSHandle<JSTaggedValue> deleteString(factory->NewFromCanBeCompressString("delete"));
    JSHandle<JSTaggedValue> addString(factory->NewFromCanBeCompressString("add"));
    JSHandle<JSTaggedValue> defaultString = thread_->GlobalConstants()->GetHandledDefaultString();
    JSHandle<JSTaggedValue> deleteMethod = JSObject::GetMethod(thread_, weakSet, deleteString);
    JSHandle<JSTaggedValue> addMethod = JSObject::GetMethod(thread_, weakSet, addString);

    JSMethod *invokeSelf =
        vm_->GetMethodForNativeFunction(reinterpret_cast<void *>(builtins::function::FunctionPrototypeInvokeSelf));
    JSMethod *ctor = vm_->GetMethodForNativeFunction(reinterpret_cast<void *>(builtins::function::FunctionConstructor));

    JSHandle<JSHClass> protoDynclass = JSHandle<JSHClass>::Cast(env->GetFunctionClassWithProto());
    JSHandle<JSFunction> funcFuncPrototype = factory->NewJSFunctionByDynClass(invokeSelf, protoDynclass);
    // add method in funcPrototype
    PropertyDescriptor desc = PropertyDescriptor(thread_, deleteMethod);
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(funcFuncPrototype), deleteString, desc);
    JSHandle<JSTaggedValue> funcFuncPrototypeValue(funcFuncPrototype);

    JSHandle<JSHClass> funcFuncProtoIntanceDynclass =
        factory->CreateDynClass<JSFunction>(JSType::JS_FUNCTION, funcFuncPrototypeValue);
    // new with NewJSFunctionByDynClass::function DynClass
    JSHandle<JSFunction> protoFunc =
        factory->NewJSFunctionByDynClass(ctor, funcFuncProtoIntanceDynclass, FunctionKind::BUILTIN_CONSTRUCTOR);
    EXPECT_TRUE(*protoFunc != nullptr);
    // add method in funcnction
    PropertyDescriptor desc1 = PropertyDescriptor(thread_, addMethod);
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(protoFunc), addString, desc1);
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(protoFunc), deleteString, desc);
    // father type
    Local<FunctionRef> protoLocal = JSNApiHelper::ToLocal<FunctionRef>(JSHandle<JSTaggedValue>(protoFunc));

    JSHandle<JSHClass> noProtoDynclass = JSHandle<JSHClass>::Cast(env->GetFunctionClassWithoutProto());
    JSHandle<JSFunction> funcFuncNoProtoPrototype = factory->NewJSFunctionByDynClass(invokeSelf, noProtoDynclass);
    JSHandle<JSTaggedValue> funcFuncNoProtoPrototypeValue(funcFuncNoProtoPrototype);

    JSHandle<JSHClass> funcFuncNoProtoProtoIntanceDynclass =
        factory->CreateDynClass<JSFunction>(JSType::JS_FUNCTION, funcFuncNoProtoPrototypeValue);
    // new with NewJSFunctionByDynClass::function DynClass
    JSHandle<JSFunction> noProtoFunc =
        factory->NewJSFunctionByDynClass(ctor, funcFuncNoProtoProtoIntanceDynclass, FunctionKind::BUILTIN_CONSTRUCTOR);
    EXPECT_TRUE(*noProtoFunc != nullptr);
    // set property that has same key with fater type
    PropertyDescriptor desc2 = PropertyDescriptor(thread_, defaultString);
    JSObject::DefineOwnProperty(thread_, JSHandle<JSObject>::Cast(noProtoFunc), addString, desc2);
    // son type
    Local<FunctionRef> noProtoLocal = JSNApiHelper::ToLocal<FunctionRef>(JSHandle<JSTaggedValue>(noProtoFunc));

    noProtoLocal->Inherit(vm_, protoLocal);

    JSHandle<JSFunction> sonHandle = JSHandle<JSFunction>::Cast(JSNApiHelper::ToJSHandle(noProtoLocal));
    OperationResult res = JSObject::GetProperty(thread_, JSHandle<JSObject>::Cast(sonHandle), deleteString);
    EXPECT_EQ(JSTaggedValue::SameValue(deleteMethod, res.GetValue()), true);
    // test if the property value changed after inherit
    OperationResult res1 = JSObject::GetProperty(thread_, JSHandle<JSObject>::Cast(sonHandle), addString);
    EXPECT_EQ(JSTaggedValue::SameValue(defaultString, res1.GetValue()), true);
}

TEST_F(JSNApiTests, ClassFunction)
{
    LocalScope scope(vm_);
    Local<FunctionRef> cls = FunctionRef::NewClassFunction(vm_, nullptr, nullptr, nullptr);

    JSHandle<JSTaggedValue> clsObj = JSNApiHelper::ToJSHandle(Local<JSValueRef>(cls));
    ASSERT_TRUE(clsObj->IsClassConstructor());

    JSTaggedValue accessor =
        JSHandle<JSFunction>(clsObj)->GetPropertyInlinedProps(JSFunction::CLASS_PROTOTYPE_INLINE_PROPERTY_INDEX);
    ASSERT_TRUE(accessor.IsInternalAccessor());
}
}  // namespace ark::test

// NOLINTEND(readability-magic-numbers,modernize-avoid-c-arrays)
